package lumis.doui.table.filter;

import lumis.doui.DouiException;
import lumis.doui.source.field.ISourceField;
import lumis.doui.source.field.type.ISourceFieldDataType;
import lumis.doui.source.filter.RequiredFilterValueException;
import lumis.doui.table.TableSource;
import lumis.doui.table.field.ITableSourceField;
import lumis.portal.PortalException;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.serviceinstance.ServiceInstanceConfig;
import lumis.portal.stability.StableMinor;
import lumis.portal.transaction.PortalTransactionFactory;
import lumis.util.ITransaction;
import lumis.util.JdbcUtil;
import lumis.util.XmlUtil;
import lumis.util.parameter.IParameters;
import lumis.util.query.IQueryFilter;
import lumis.util.query.IQueryValue;
import lumis.util.query.QueryBase;
import lumis.util.query.QueryField;
import lumis.util.query.QueryFilter;
import lumis.util.query.QueryValue;

import java.util.Date;

import org.w3c.dom.Node;

/**
 * Represents a source filter. Interacts with source fields to generate {@link IQueryFilter}.
 * 
 * @since 4.0.4
 * @version $Revision: 13082 $ $Date: 2011-05-27 16:03:12 -0300 (Fri, 27 May 2011) $
 */
@StableMinor(version = "6.0", sinceVersion = "4.0")
public class TableSourceFilter extends AbstractTableSourceFilter
{
	private Node filterNode;
	private IParameters parameters;
	private String fieldId;
	private boolean hideAllResults;
	private boolean valueInLookup;
	private boolean orNull;
	private IQueryValue filterValue;
	private String operator;
	private boolean required;
	
	public void init(TableSource source, Node filterNode) throws PortalException
	{
		super.init(source, filterNode);
		this.filterNode = filterNode;
		this.hideAllResults = false;
		setDefinition(filterNode);
	}
	
	public void init(TableSource source, String fieldId, String operator, IQueryValue filterValue) throws PortalException
	{
		this.filterNode = null;
		setSource(source);
		this.fieldId = fieldId;
		this.operator = operator;
		this.filterValue = filterValue;
		setId("temp");
	}
	
	public void setDefinition(Node filterDefinitionNode) throws PortalException
	{
		setId(XmlUtil.readAttributeString("id", filterDefinitionNode));
		
		String filterFieldId;
		if(XmlUtil.readAttributeBoolean("selectField", filterDefinitionNode))
		{
			Node fieldNode = XmlUtil.selectSingleNode("selectFields/field", filterDefinitionNode);
			if(fieldNode == null)
				throw new DouiException("No select fields found in filter with id: "+getId());
			
			filterFieldId = XmlUtil.readAttributeString("id", fieldNode);
		}
		else
		{
			filterFieldId =  XmlUtil.readAttributeString("fieldId", filterDefinitionNode, getId());
		}
		
		setFieldId(filterFieldId);

		setRequired(XmlUtil.readAttributeBoolean("required", filterDefinitionNode, false));
		setOrNull(XmlUtil.readAttributeBoolean("orNull", filterDefinitionNode, false));
		setOperator(calculateFilterOperator(filterDefinitionNode));
		
		// parse valueInLookup
		boolean valueInLookup = true;
		boolean valueInLookupSpecified = (XmlUtil.readAttributeString("valueInLookup", filterDefinitionNode) != null);
		if(valueInLookupSpecified)
		{
			valueInLookup = XmlUtil.readAttributeBoolean("valueInLookup", filterDefinitionNode, false);
		}
		else
		{
			// guess whether value is in lookup based on the filter type
			if (XmlUtil.readAttributeString("showItems", filterDefinitionNode, null) != null || XmlUtil.readAttributeBoolean("hidden", filterDefinitionNode))
				valueInLookup = false;
		}			
		setValueInLookup(valueInLookup);
	}
	
	/**
	 * Returns a query filter corresponding to this source filter definition.
	 * @param queryBase the query base the query filter will be applied to.
	 * @param parameters parameters to be used in the query filter.
	 * @return the query filter.
	 * @throws PortalException
	 * @since 4.0.4
	 */
	public IQueryFilter getQueryFilter(QueryBase queryBase, IParameters params) throws PortalException, RequiredFilterValueException
	{	
		// set parameters and filter value
		this.parameters = params;
		if (filterNode != null)
			setFilterValue(calculateFilterValue(filterNode));
		
		// TODO: operator should be, instead of String, an object with methods like
		// isUnary, isBinary, isValueAnArray
		
		hideAllResults = false;

		// if value was not specified and the operator is binary
		if (getFilterValue() == null &&
				!IQueryFilter.FILTER_OPERATOR_IS_NULL.equals(getOperator()) &&
				!IQueryFilter.FILTER_OPERATOR_IS_NOT_NULL.equals(getOperator()))
		{
			// if required, then no results should be shown
			if (isRequired())
				hideAllResults = true;
			else
			{
				// if isNullIfNotSpecified, use operator IS NULL
				if (XmlUtil.readAttributeBoolean("nullIfNotSpecified", filterNode, false))
					setOperator(IQueryFilter.FILTER_OPERATOR_IS_NULL);
			}
		}

		if (shouldHideAllResults())
			throw new RequiredFilterValueException();
		else
		{
			// TODO: operator should be, instead of String, an object with methods like
			// isUnary, isBinary, isValueAnArray
			
			// if operator is binary and value is null, return null
			if (!IQueryFilter.FILTER_OPERATOR_IS_NULL.equals(operator) &&
					!IQueryFilter.FILTER_OPERATOR_IS_NOT_NULL.equals(operator))
			{
				if (filterValue == null)
					return null;
				
				if (filterValue instanceof QueryValue)
				{
					Object value = ((QueryValue)filterValue).getValue();
					if (value == null)
						return null;
				}
			}
			
			String sourceFieldId = fieldId;
			Object sourceParam = getSource().getParameterValue((getId()+".fieldId"));
			if(sourceParam != null)
				sourceFieldId = sourceParam.toString();

			// ask the source field for the QueryFilter
			ITableSourceField field = getSource().getField(sourceFieldId);
			if(field == null)
				throw new DouiException("STR_FIELD_NOT_FOUND;" + sourceFieldId);
			return field.getQueryFilter(queryBase, this);
		}
	}
	
	/**
	 * Creates a query filter, filtering the query field by the value, according
	 * to this filter definitions. 
	 * @param queryField the query field.
	 * @param queryValue the query value.
	 * @return the query filter.
	 * @since 4.0.4
	 */
	public IQueryFilter getQueryFilter(QueryField queryField, IQueryValue queryValue) throws PortalException
	{
		QueryFilter queryFilter = new QueryFilter();
		queryFilter.setLeftField(queryField);
		queryFilter.setFilterOperator(getOperator());
		queryFilter.setRightField(queryValue);
		queryFilter.setOrNull(isOrNull());
		return queryFilter;		
	}
	
	public String getInputControlType() throws PortalException
	{
		ISourceField sourceField = getSource().getField(getFieldId());
		
		if(sourceField != null)
			return sourceField.getDataType().getDefaultInputControlType();
		
		return super.getInputControlType();
	}

	/**
	 * Analyzes this filter and returns the operator that should be used with it.
	 * @param filterNode the filter definition node.
	 * @return the filter operator.
	 * @throws PortalException
	 * @since 4.0.4
	 */
	protected String calculateFilterOperator(Node filterNode) throws PortalException
	{
		String operator = XmlUtil.readAttributeString("operator", filterNode);

		// if no operator specified, try get operator from source parameters
		if (operator == null)
			operator = (String) getSource().getParameterValue(getId() + ".operator");

		// if still no operator found, try get operator from operators
		// definition
		if (operator == null)
		{
			Node operators = XmlUtil.selectSingleNode("operators", filterNode);

			if (operators != null)
			{
				Node defaultOperator = XmlUtil.selectSingleNode("operator[@isDefault='true']", operators);
				Node firstOperator = XmlUtil.selectSingleNode("operator", operators);

				if (defaultOperator != null)
					operator = XmlUtil.readAttributeString("value", defaultOperator);

				else if (firstOperator != null)
					operator = XmlUtil.readAttributeString("value", firstOperator);
			}
		}

		operator = convertOperatorStandardValue(operator);
		
		return operator;
	}

	protected String convertOperatorStandardValue(String operator)
	{
		if (operator == null)
			operator = IQueryFilter.FILTER_OPERATOR_EQUAL;

		else if (operator.equals("equal"))
			operator = IQueryFilter.FILTER_OPERATOR_EQUAL;

		else if (operator.equals("notEqual"))
			operator = IQueryFilter.FILTER_OPERATOR_NOT_EQUAL;

		else if (operator.equals("greaterThan"))
			operator = IQueryFilter.FILTER_OPERATOR_GREATER_THAN;

		else if (operator.equals("greaterThanOrEqual"))
			operator = IQueryFilter.FILTER_OPERATOR_GREATER_THAN_OR_EQUAL;

		else if (operator.equals("lessThan"))
			operator = IQueryFilter.FILTER_OPERATOR_LESS_THAN;

		else if (operator.equals("lessThanOrEqual"))
			operator = IQueryFilter.FILTER_OPERATOR_LESS_THAN_OR_EQUAL;

		else if (operator.equals("like"))
			operator = IQueryFilter.FILTER_OPERATOR_LIKE;

		else if (operator.equals("likeCaseSensitive"))
			operator = IQueryFilter.FILTER_OPERATOR_LIKE_CASE_SENSITIVE;
		
		else if (operator.equals("in"))
			operator = IQueryFilter.FILTER_OPERATOR_IN;

		else if (operator.equals("isNull"))
			operator = IQueryFilter.FILTER_OPERATOR_IS_NULL;
		
		else if (operator.equals("isNotNull"))
			operator = IQueryFilter.FILTER_OPERATOR_IS_NOT_NULL;
		
		else
			operator = IQueryFilter.FILTER_OPERATOR_EQUAL;

		return operator;
	}

	/**
	 * Analyzes this filter and returns the value that should be used with it.
	 * @param filterNode the filter definition node.
	 * @return the filter value.
	 * @throws PortalException
	 * @since 4.0.4
	 */
	protected IQueryValue calculateFilterValue(Node filterNode) throws PortalException
	{
		// first see if the value is specified in the definition
		Object value = XmlUtil.readAttributeString("value", filterNode);
		if (value != null)
		{
			// convert the value from the definition string to the java object 
			if (value instanceof String)
			{
				// make the conversion of standard values		
				value = convertFilterStandardValue((String)value, filterNode);				
			}

			// if associated with an existing field
			if (fieldId != null)
			{
				ISourceField field = getSource().getField(fieldId);
				if (field != null)
				{
					// do the conversion using field's type definition
					ISourceFieldDataType dataType = field.getDataType();
					value = field.getConverter().convert(field.getValueClass(), 
							value, dataType.getDefinitionLocale(),
							dataType.getDefinitionPattern());
				}
			}
		}

		// if no value specified, check if specified in the request
		// parameter
		if (value == null)
			value = getSource().getParameterValue(getId());
		
		// if no value specified, check if the parameters have a value.
		// this is used in multi-row process action handler
		if(parameters != null)
		{
			if(value == null)
				value = parameters.getParameter(getId());
		}

		if (value != null)
		{
			if (value instanceof String)
			{
				// make the conversion of standard values		
				value = convertFilterStandardValue((String)value, filterNode);				
			}
			return new QueryValue(value);
		}
		else
			return null;
	}

	/**
	 * Checks and converts the filter "standard" value to real value.  
	 * @param value
	 * @param filterNode
	 * @return
	 * @throws PortalException
	 * @since 4.0.4
	 */
	protected Object convertFilterStandardValue(String value, Node filterNode) throws PortalException
	{
		Object resultValue = value;

		if (resultValue.equals("lum_currentChannel"))
		{
			resultValue = getSource().getDouiContext().getRequest().getPageConfig().getChannelId();
		}
		else if (resultValue.equals("lum_currentPage"))
		{
			resultValue = getSource().getDouiContext().getRequest().getPageConfig().getId();
		}
		else if (resultValue.equals("lum_currentServiceInstanceId"))
		{
			ServiceInstanceConfig serviceInstanceConfig = getSource().getDouiContext().getRequest().getServiceInstanceConfig();
			resultValue = serviceInstanceConfig != null ? serviceInstanceConfig.getId() : null;
		}
		else if (resultValue.equals("lum_currentServiceInstanceChannelId"))
		{
			ServiceInstanceConfig serviceInstanceConfig = getSource().getDouiContext().getRequest().getServiceInstanceConfig();
			resultValue = serviceInstanceConfig != null ? serviceInstanceConfig.getChannelId() : null;
			if (resultValue == null)
				resultValue = getSource().getDouiContext().getRequest().getPageConfig().getChannelId();
		}
		else if (resultValue.equals("lum_currentUser"))
		{
			ITransaction transaction = PortalTransactionFactory.createTransaction();
			try
			{
				transaction.begin();
				resultValue = getSource().getSourceContext().getSessionConfig().getUserId();
				transaction.commit();
			}
			finally
			{
				transaction.dispose();
			}
		}
		else if (resultValue.equals("lum_callerInterfaceInstanceId"))
		{
			resultValue = getSource().getDouiContext().getRequest().getCallerServiceInterfaceInstanceId();
		}
		else if (resultValue.equals("lum_callerInterfaceId"))
		{
			ITransaction transaction = PortalTransactionFactory.createTransaction();
			try
			{
				transaction.begin();
				String callerInterfaceInstanceId = getSource().getDouiContext().getRequest().getCallerServiceInterfaceInstanceId();
				resultValue = (ManagerFactory.getServiceInterfaceInstanceManager().get(getSource().getDouiContext().getRequest().getSessionConfig(), callerInterfaceInstanceId, transaction)).getServiceInterfaceId();
				transaction.commit();
			}
			finally
			{
				transaction.dispose();
			}
		}
		else if (resultValue.equals("lum_currentDateTime"))
		{
			Date curDateTime = new Date();
			resultValue = JdbcUtil.getTimestampByDate(curDateTime);
		}

		return resultValue;
	}

	public boolean shouldHideAllResults()
	{
		return hideAllResults;
	}

	public String getFieldId()
	{
		return fieldId;
	}

	public void setFieldId(String fieldId)
	{
		this.fieldId = fieldId;
	}

	public String getOperator()
	{
		return operator;
	}

	public void setOperator(String operator)
	{
		this.operator = operator;
	}

	public boolean isOrNull()
	{
		return orNull;
	}

	public void setOrNull(boolean orNull)
	{
		this.orNull = orNull;
	}

	public IParameters getParameters()
	{
		return parameters;
	}

	public void setParameters(IParameters parameters)
	{
		this.parameters = parameters;
	}

	public boolean isRequired()
	{
		return required;
	}

	public void setRequired(boolean required)
	{
		this.required = required;
	}

	public IQueryValue getFilterValue()
	{
		return filterValue;
	}

	public void setFilterValue(IQueryValue value)
	{
		this.filterValue = value;
	}

	public boolean isValueInLookup()
	{
		return valueInLookup;
	}

	public void setValueInLookup(boolean valueInLookup)
	{
		this.valueInLookup = valueInLookup;
	}
}
